32. 长三角上市公司因子聚类(信用卡样例对照)

本章概要

  • 学习材料:长三角公司季度估值/盈利因子截面。
  • 本章任务:运行 lst-ch32-local-features 与聚类结果判断,比较K=2—6和相邻季ARI。
  • 完成后你将得到:轮廓系数、最小簇占比、ARI、随机基线与暂不采用模型状态。
  • 自我检查:核对标准化只用当期截面且簇标签匹配;共同样本少于20时先检查原因。
  • 拓展练习:拓展应用到汽车制造业公司簇,不得写成客户营销。

本节学习目标

  • 理解 K-Means 聚类算法 的核心思想
  • 掌握公司估值因子截面的 预处理流程;信用卡页仅为受保护平台 API 对照
  • 学会使用 sklearn 进行 K-Means 聚类建模
  • 能够对聚类结果进行 可视化业务解读

什么是聚类分析?

聚类 是一种 无监督学习 方法:

  • 无需标签,自动发现数据中的 内在结构
  • 将相似样本归为同一组(
  • 不同簇之间的样本差异尽可能大

本章主应用:公司研究覆盖分组、估值结构描述与异常截面核对;不生成客户级营销动作。

公司因子聚类的研究价值

公司因子聚类研究目标 展示公司因子聚类在研究覆盖、异常核对和分层抽样三个方面的用途 👥 研究覆盖 识别因子结构 分层选择样本 避免只看龙头 🛡️ 异常核对 检查极端因子 核对口径缺失 暂停异常外推 🎯 分层再次检查 季度重算标签 比较簇稳定性 记录需要重新检查的情况

K-Means 算法核心思想

K-Means算法流程 展示K-Means算法从初始化到收敛的四个关键步骤 Step 1 随机选取 K 个初始 聚类中心 Step 2 计算每个样本 到中心的距离 分配到最近簇 Step 3 重新计算 每个簇的 质心位置 Step 4 是否收敛? 是→结束 否→回Step2 未收敛:返回 Step 2

中国真实数据验证:输入口径与判断条件

要素 课堂口径
本地资产 valuation_factors_quarterly_15_years.h5stock_basic_data
样本 最新报告期沪苏浙皖截面约1965家
特征 EP、BM、股息率、对数市值;截面z标准化
方法 K=2—6 轮廓系数选优;ARI核对稳定性

课堂判断标准:轮廓系数 ≥ 0.20;ARI ≥ 0.60;最小组占比 ≥ 10%;并且优于随机分组。任一标准不满足时,不生成观察名单。

中国真实数据验证:数据定位与长三角筛选

Listing 1
展开真实数据定位与筛选代码
from pathlib import Path  # 导入路径工具以探测课程数据挂载
import numpy as np  # 导入数值计算库
import pandas as pd  # 导入数据分析库
from sklearn.cluster import KMeans  # 导入K-Means聚类
from sklearn.metrics import silhouette_score, adjusted_rand_score  # 导入轮廓系数与ARI评估
candidate_roots = [Path('/home/ubuntu/r2_data_mount/data'), Path(r'C:\qiufei\data')]  # 按课程规定顺序探测数据根目录
data_root = next((root for root in candidate_roots if root.is_dir()), None)  # 选择第一个可用根目录
factor_path = data_root / 'stock' / 'valuation_factors_quarterly_15_years.h5' if data_root else None  # 定位估值因子季度表
if factor_path is None or not factor_path.is_file():  # 真实数据缺失时明确提示
    raise FileNotFoundError(f'未找到估值因子文件:{factor_path};请从课程数据下载入口获取')  # 提示读者下载文件
valuation_panel = pd.read_hdf(factor_path, key='valuation_factors')  # 读取真实A股估值因子面板
basic_path = data_root / 'stock' / 'stock_basic_data.h5'  # 定位公司基础信息文件
company_basic = pd.read_hdf(basic_path, key='stock_basic_info')  # 使用实际 HDF 键读取公司基础信息
province_column = next((column for column in ['province', '省份'] if column in company_basic.columns), None)  # 探测省份字段名
if province_column is None:  # 字段要求不满足时暂停
    raise KeyError('公司基础信息缺少省份字段,无法筛选长三角样本')  # 说明缺少的字段
delta_company_codes = set(company_basic.loc[company_basic[province_column].isin(['上海市', '浙江省', '江苏省', '安徽省']), 'order_book_id'])  # 沪苏浙皖公司代码集合
print(f'数据就绪:估值因子{len(valuation_panel)}行|长三角公司{len(delta_company_codes)}家')  # 报告输入口径
数据就绪:估值因子207600行|长三角公司2055家

中国真实数据验证:截面特征构建

Listing 2
展开报告期截面标准化代码
factor_columns = ['ep_ratio_ttm', 'book_to_market_ratio_lf', 'dividend_yield_ttm', 'market_cap']  # 盈利收益/价值/股息/规模四因子
quarter_list = sorted(valuation_panel.index.get_level_values('date').unique())  # 面板内全部报告期
quarter_now, quarter_prev = quarter_list[-1], quarter_list[-2]  # 最新与上一报告期
def build_quarter_features(quarter):  # 构建某报告期的标准化长三角截面
    cross_section = valuation_panel.loc[(slice(None), quarter), factor_columns].dropna().reset_index('date', drop=True)  # 当季完整样本并还原公司单层索引
    cross_section = cross_section[cross_section.index.isin(delta_company_codes)]  # 仅保留沪苏浙皖公司
    cross_section = cross_section.assign(log_market_cap=np.log(cross_section['market_cap'])).drop(columns='market_cap')  # 市值对数化压缩尺度
    return (cross_section - cross_section.mean()) / cross_section.std()  # 截面内z分数标准化
x_now = build_quarter_features(quarter_now)  # 当季特征矩阵(不含簇标签)
x_prev = build_quarter_features(quarter_prev)  # 上季特征矩阵(稳定性核对用)
common_codes = x_now.index.intersection(x_prev.index)  # 两季共有公司代码
print(f'截面:{quarter_now.date()}{len(x_now)}家|{quarter_prev.date()}{len(x_prev)}家|共有{len(common_codes)}家')  # 报告样本边界
截面:2025-12-31共1965家|2025-09-30共1943家|共有1943家

中国真实数据验证:K选择与稳定性核对

轮廓系数衡量簇内紧凑与簇间分离(越接近1越清晰),用于在 K=2—6 中选优;ARI(调整兰德指数)衡量两次分组的一致性:1=完全一致,0≈随机,负值=更差,要求相邻季度 ≥ 0.60 才认为簇结构稳定。

Listing 3
展开K扫描与相邻季度稳定性代码
silhouette_by_k = {}  # 存放每个K的轮廓系数
for cluster_count in range(2, 7):  # 扫描K=2到6
    fitted_labels = KMeans(n_clusters=cluster_count, n_init=10, random_state=42).fit_predict(x_now)  # 固定随机种子拟合
    silhouette_by_k[cluster_count] = silhouette_score(x_now, fitted_labels)  # 记录该K的轮廓系数
best_k = max(silhouette_by_k, key=silhouette_by_k.get)  # 取轮廓系数最高的K
kmeans_now = KMeans(n_clusters=best_k, n_init=10, random_state=42).fit(x_now)  # 用最优K拟合当季截面
silhouette_now = silhouette_by_k[best_k]  # 当季轮廓系数
random_baseline = silhouette_score(x_now, np.random.default_rng(42).permutation(kmeans_now.labels_))  # 随机标签参照基线
kmeans_prev = KMeans(n_clusters=best_k, n_init=10, random_state=42).fit(x_prev.loc[common_codes])  # 上一季度同K拟合
stability_ari = adjusted_rand_score(kmeans_prev.labels_, kmeans_now.predict(x_now.loc[common_codes]))  # 相邻季度调整兰德指数
cluster_share = pd.Series(kmeans_now.labels_).value_counts(normalize=True).sort_index()  # 各簇样本占比
print('各K轮廓系数:' + ','.join(f'K={k}:{v:.3f}' for k, v in silhouette_by_k.items()))  # 展示K扫描结果
print(f'最优K={best_k}|轮廓系数{silhouette_now:.3f}(随机基线{random_baseline:.3f})|相邻季度ARI={stability_ari:.3f}')  # 展示验证指标
print('各簇样本占比:' + ','.join(f'簇{label}:{share:.1%}' for label, share in cluster_share.items()))  # 展示簇规模
各K轮廓系数:K=2:0.412,K=3:0.278,K=4:0.300,K=5:0.310,K=6:0.261
最优K=2|轮廓系数0.412(随机基线0.023)|相邻季度ARI=0.838
各簇样本占比:簇0:80.8%,簇1:19.2%

中国真实数据验证:簇画像与预声明判断条件

Listing 4
展开簇画像、检查要求与条件行动代码
raw_cross_section = valuation_panel.loc[(slice(None), quarter_now), factor_columns].dropna().reset_index('date', drop=True).loc[x_now.index]  # 当季原始尺度截面并对齐样本
cluster_profile = raw_cross_section.groupby(kmeans_now.labels_).median()  # 各簇特征中位数画像
cluster_profile['market_cap_billion'] = cluster_profile['market_cap'] / 1e9  # 总市值换算为十亿元
cluster_size = pd.Series(kmeans_now.labels_).value_counts().sort_index()  # 各簇公司数
silhouette_limit, stability_limit, share_limit = 0.20, 0.60, 0.10  # 预声明的三条检查要求阈值
conditions_met = silhouette_now >= silhouette_limit and stability_ari >= stability_limit and cluster_share.min() >= share_limit and silhouette_now > random_baseline  # 四条件同时满足才通过
decision = '检查要求通过:按簇画像构建课堂观察池(不构成买卖指令)' if conditions_met else '检查要求未通过:暂停观察池输出'  # 相应建议
watchlist_text = ';'.join(f'簇{label}入选{size}家' for label, size in cluster_size.items()) if conditions_met else '不输出观察池'  # 符合条件时生成的观察名单规模
print(cluster_profile.round(3).to_string())  # 显示簇画像表
print(f'检查要求(轮廓≥{silhouette_limit}、ARI≥{stability_limit}、最小簇≥{share_limit:.0%}、优于随机基线):{"通过" if conditions_met else "暂停"}')  # 显示检查要求结论
print(f'{decision}{watchlist_text}|口径:{quarter_now.date()}截面,换报告期或因子集须复算')  # 显示条件行动
   ep_ratio_ttm  book_to_market_ratio_lf  dividend_yield_ttm    market_cap  market_cap_billion
0         0.011                    0.298               0.004  5.920678e+09               5.921
1         0.053                    0.673               0.031  1.065165e+10              10.652
检查要求(轮廓≥0.2、ARI≥0.6、最小簇≥10%、优于随机基线):通过
检查要求通过:按簇画像构建课堂观察池(不构成买卖指令)|簇0入选1587家;簇1入选378家|口径:2025-12-31截面,换报告期或因子集须复算

中国真实数据验证:因子簇散点图

展开散点图绘制代码
import matplotlib.pyplot as plt  # 导入绘图库
figure, axis = plt.subplots(figsize=(7.0, 3.8))  # 创建适配幻灯片的横向画布
x_lower, x_upper = x_now['book_to_market_ratio_lf'].quantile([0.01, 0.99])  # 横轴1%—99%分位限
y_lower, y_upper = x_now['ep_ratio_ttm'].quantile([0.01, 0.99])  # 纵轴1%—99%分位限
axis.set_xlim(x_lower, x_upper)  # 设横轴范围压缩极端值
axis.set_ylim(y_lower, y_upper)  # 设纵轴范围压缩极端值
scatter_handle = axis.scatter(x_now['book_to_market_ratio_lf'], x_now['ep_ratio_ttm'], c=kmeans_now.labels_, cmap='viridis', s=12, alpha=0.5)  # 公司散点按簇着色
axis.scatter(kmeans_now.cluster_centers_[:, 1], kmeans_now.cluster_centers_[:, 0], c='#EC232A', marker='X', s=220, edgecolors='black', linewidths=1.5)  # 红色X标记质心(列序:EP,BM,股息,对数市值)
axis.set_xlabel('B/M ratio (z-score)')  # 横轴为账面市值比标准化值
axis.set_ylabel('EP ratio (z-score)')  # 纵轴为盈利收益率标准化值
axis.grid(True, alpha=0.3)  # 显示浅网格
figure.colorbar(scatter_handle, ax=axis, label='Cluster')  # 添加簇编号颜色条
figure.tight_layout()  # 自动收紧边距
plt.show()  # 显示图形
图中展示长三角公司估值因子截面K-Means聚类(真实数据,标准化特征);读者应依据坐标、图例与注释比较主要模式。
Figure 1: 长三角公司估值因子截面K-Means聚类(真实数据,标准化特征)

簇画像的业务解读

  • 高账面市值比、高盈利收益、高股息、市值偏大的公司构成“价值—分红”画像;低账面市值比、低盈利收益、市值偏小的公司构成“成长—小盘”画像,历史波动通常更高。
  • 口径统一:轮廓系数0.412位于本章阈值图的“中等”区间,判断条件阈值0.20只是课堂可接受下限——通过表示分组达到课堂练习标准,并非投资适配结论。
  • 观察池按簇整体输出用于分组跟踪练习(如簇内再分层应另行说明规则),不构成任何买卖指令;更换报告期、因子集或区域筛选后必须复算判断条件。

数据准备:导入库与读取数据

展开完整代码(投影默认折叠)
# 注:09_CC GENERAL.csv数据文件本地没有,但平台已经内置;本块为平台流程对照,课堂依据以“中国真实数据验证”链为准

# ==================== 导入必要的库 ====================
import pandas as pd          # 数据处理库
import numpy as np           # 数值计算库
from sklearn.cluster import KMeans          # K-Means聚类算法
from sklearn.metrics import silhouette_score # 轮廓系数评估指标
import matplotlib.pyplot as plt             # 绘图库
import seaborn as sns                       # 统计绘图库

# ==================== 读取数据 ====================
# 读取信用卡用户数据,包含用户ID和各种消费行为特征
data = pd.read_csv('09_CC GENERAL.csv')

# ==================== 数据探索 ====================
print('数据形状:', data.shape)
print('\n数据预览:')
print(data.head())

数据清洗:删除无关列与处理缺失值

# 注:依赖上方未执行的平台数据读取;本块为平台流程对照,课堂依据以“中国真实数据验证”链为准

# ==================== 数据清洗 ====================
# 删除客户ID列,因为聚类算法不应受ID影响
data_clean = data.drop(['CUST_ID'], axis=1)

# ==================== 处理缺失值 ====================
# 使用前向填充方法处理缺失值
data_clean.fillna(method='ffill', inplace=True)

print(f'缺失值处理后剩余缺失值: {data_clean.isnull().sum().sum()}')

# ==================== 数据描述统计 ====================
print('\n数据描述统计:')
print(data_clean.describe())

运行前预测|K-Means 聚类建模(平台任务)

  • 输入预测:运行前先写出 datakmeanscluster_summary 的业务含义、数据类型或取值范围,并判断哪一个输入最可能改变结果。
  • 结果预测:不展开答案,先预测将得到cluster_summary 的结果;同时写出方向、数量级或表格/图形结构。
  • 完成要求:能独立说明本任务从输入到“K-Means 聚类建模(平台任务)”结果的关键步骤,原样录入平台代码并得到可核对的运行结果。

⭐ K-Means 聚类建模(平台任务)

展开完整代码(投影默认折叠)

# 注:09_CC GENERAL.csv数据文件本地没有,但平台已经内置
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import pandas as pd  # 导入Pandas数据分析库
data = pd.read_csv('09_CC GENERAL.csv')  # 从CSV文件读取数据存入data
 
# 数据处理
data.drop(['CUST_ID'], axis=1, inplace=True)
data.fillna(method='ffill', inplace=True)  # 使用前向填充法处理缺失值
 
from sklearn.cluster import KMeans  # 导入Scikit-learn的KMeans模块
import matplotlib.pyplot as plt  # 导入Matplotlib绘图库
 
# 使用Kmeans算法对数据进行聚类
kmeans = KMeans(n_clusters=5, n_init=10, max_iter=300,algorithm="elkan", random_state=42)
kmeans.fit(data)  # 在数据上训练kmeans模型
 
# 可视化展示聚类结果
plt.scatter(data.iloc[:, 0], data.iloc[:, 1], c=kmeans.labels_, cmap='viridis')
plt.savefig("s.png")  # 保存图形至文件
plt.show()  # 显示图形
 
# 分析不同类别的用户特征和行为差异
data['cluster'] = kmeans.labels_
cluster_summary = data.groupby('cluster').mean()  # 按指定列分组聚合
print(cluster_summary)  # 输出聚类数据
 
# 对聚类结果进行评估
from sklearn.metrics import silhouette_score
print(silhouette_score(data, kmeans.labels_))  # 输出均值数据

任务复盘|K-Means 聚类建模(平台任务)

运行后核对:核对 datakmeanscluster_summary 是否按预测参与运算,实际输出是否与预测一致;若不一致,先检查类型、单位、索引/字段和运算顺序。

拓展练习:把对象或期间改为一家长三角上市公司或一组 A 股资产;先预测指标方向,再说明结果能支持和不能支持的决策。

平台代码逐行解读(上)

代码 含义
pd.read_csv(...) 从 CSV 文件读取数据
data.drop(['CUST_ID'], ...) 删除客户 ID 列
data.fillna(method='ffill') 前向填充缺失值
KMeans(n_clusters=5, ...) 创建 5 簇的 K-Means 模型
kmeans.fit(data) 在数据上训练模型

平台代码逐行解读(下)

代码 含义
plt.scatter(...) 绘制聚类散点图
c=kmeans.labels_ 用簇标签着色
data.groupby('cluster').mean() 按簇计算各特征均值
silhouette_score(...) 计算轮廓系数评估质量

关键参数n_clusters=5(5 簇)、n_init=10(初始化 10 次取最优)、max_iter=300(最大迭代)。

聚类结果可视化:平台代码对照

平台示例用 plt.scatter(... c=kmeans.labels_ ...) 把聚类结果染成散点图;本章“中国真实数据验证:因子簇散点图”页在真实 A 股截面上执行了同样的可视化,并补充了质心标记与分位数轴限。

聚类评估:轮廓系数

轮廓系数解读 展示轮廓系数从-1到1的范围及不同区间的聚类质量解读 轮廓系数 (Silhouette Score) 解读 -1 0.2 0.5 1 < 0.2:聚类效果较差,簇间重叠严重 0.2 - 0.5:中等聚类,可考虑调整 K 值 0.5 - 0.7:良好聚类,样本分配合理 > 0.7:优秀聚类,簇间分离度高

轮廓系数的正确计算口径

  • 平台示例在 data['cluster'] = kmeans.labels_ 之后才调用 silhouette_score(data, kmeans.labels_),簇标签列被一并当作特征,属于标签泄漏
  • 正确口径:轮廓系数只在不含簇标签的特征矩阵上计算;本章真实数据处理过程的 x_now 只含标准化因子,不含任何标签列。
  • 同理,评估分组差异、给业务方提供画像时,应先剔除由标签派生的列,再计算统计量。

真实截面的轮廓系数分级

Listing 5
展开轮廓系数分级解读代码
if silhouette_now > 0.5:  # 高分离区间
    silhouette_verdict = '聚类效果良好,簇间分离度高'  # 对应上图蓝色区间
elif silhouette_now > 0.2:  # 中等区间
    silhouette_verdict = '聚类效果中等,建议核对特征与K'  # 对应上图橙色区间
else:  # 低分离区间
    silhouette_verdict = '聚类效果较差,应重选特征或算法'  # 对应上图红色区间
print(f'真实截面轮廓系数{silhouette_now:.3f}{silhouette_verdict}(分级阈值与上一图一致)')  # 用真实数值对照分级标准
真实截面轮廓系数0.412:聚类效果中等,建议核对特征与K(分级阈值与上一图一致)

商业应用:公司因子簇的研究动作

公司因子研究动作矩阵 基于价值因子与盈利收益率的四象限公司研究动作 盈利收益率 → 价值因子 → 高价值·低盈利 核对一次性损益 动作:财报核对 高价值·高盈利 进入覆盖候选 动作:独立估值核对 低价值·低盈利 检查样本异常 动作:暂缓外推 低价值·高盈利 检查成长持续性 动作:季度再次检查

本节小结

  • K-Means 是经典的无监督聚类算法,迭代优化簇分配
  • 数据预处理包括 删除无关列缺失值填充
  • 轮廓系数 是评估聚类质量的关键指标,且必须在不含标签的特征矩阵上计算
  • 聚类结果需结合 业务场景 进行分群解读
  • 中国真实数据分析展示完整依据结构:本地资产 → 长三角筛选 → 验证指标 → 预声明判断条件 → 符合条件时生成的观察名单

随堂练习

  • 问题 1|怎样完成本章分析?:从 lst-ch32-local-loadlst-ch32-local-features 重新计算长三角公司因子截面,写出K=2—6轮廓系数、最小簇占比、相邻季ARI、随机基线与判断条件。
  • 问题 2|结果说明什么?:依据 lst-ch32-local-features 解释特征标准化、K选择、簇占比与跨期ARI;引用实际轮廓系数和ARI,并指出样本流失或簇标签置换的误判风险。
  • 问题 3|换一个情境,怎样继续应用?:将同一因子、K、跨期ARI和最小样本占比要求拓展应用到汽车制造业公司;共同样本少于20家或判断条件失败时输出 暂不采用模型,不得写成客户营销动作。
  • 作答提示:请完成三道题,并在回答中引用实际运行结果;拓展练习中不要把课堂示例写成普遍规律或因果结论。

教师参考解答|长三角公司因子簇

教师参考解答|答案与说明 1

  • 所用数据与字段:估值因子 HDF 与公司省份主表;最新/上一报告期,公司键 order_book_id,特征 EP, BM, dividend_yield, log_market_cap;对象是上市公司,不是客户。
  • 完整代码:运行本章 build_quarter_features 得到 x_now/x_prev/common_codes 后:

教师参考解答|代码 1

展开代码(代码区可独立滚动)
from pathlib import Path  # 导入路径工具以定位真实因子与公司快照
import numpy as np  # 导入数值工具以对数化市值并构造随机基线
import pandas as pd  # 导入表格工具以筛选公司截面
data_root=Path('/home/ubuntu/r2_data_mount/data/stock')  # 绑定课堂股票数据目录
factor_path=data_root/'valuation_factors_quarterly_15_years.h5'  # 定位季度估值因子面板
basic_path=data_root/'stock_basic_data.h5'  # 定位公司地区与行业主表
if not factor_path.exists() or not basic_path.exists(): raise FileNotFoundError('未找到课程数据文件,请从课程数据下载入口获取并核对文件位置')  # 规定资产缺失时终止聚类
valuation_panel=pd.read_hdf(factor_path,key='valuation_factors')  # 读取真实A股季度估值因子
company_basic=pd.read_hdf(basic_path,key='stock_basic_info')  # 读取公司的省份与行业信息
company_basic['province']=company_basic['province'].astype('string')  # 将省份标签统一为字符串类型
delta_company_codes=set(company_basic.loc[company_basic['province'].isin(['上海市','浙江省','江苏省','安徽省']),'order_book_id'])  # 提取长三角公司代码
factor_columns=['ep_ratio_ttm','book_to_market_ratio_lf','dividend_yield_ttm','market_cap']  # 固定盈利、价值、股息与规模因子
quarter_list=sorted(valuation_panel.index.get_level_values('date').unique())  # 排序面板内全部报告期
quarter_now,quarter_prev=quarter_list[-1],quarter_list[-2]  # 锁定最新与上一报告期

教师参考解答|代码 2

展开代码(代码区可独立滚动)
def build_quarter_features(quarter):  # 定义长三角公司截面标准化函数
    cross_section=valuation_panel.loc[(slice(None),quarter),factor_columns].dropna().reset_index('date',drop=True)  # 提取当季完整因子截面
    cross_section=cross_section.loc[cross_section.index.isin(delta_company_codes)]  # 仅保留长三角公司
    cross_section=cross_section.assign(log_market_cap=np.log(cross_section['market_cap'])).drop(columns='market_cap')  # 对数化市值并删除原尺度列
    return (cross_section-cross_section.mean())/cross_section.std()  # 在当期截面内拟合z分数尺度
x_now=build_quarter_features(quarter_now)  # 构造最新报告期特征矩阵
x_prev=build_quarter_features(quarter_prev)  # 构造上一报告期特征矩阵
common_codes=x_now.index.intersection(x_prev.index)  # 对齐两季共同公司以核对稳定性
if len(common_codes)<20: raise ValueError('当前样本不符合模型使用条件')  # 共同公司不足时终止聚类采用

教师参考解答|代码 3

展开代码(代码区可独立滚动)
from sklearn.cluster import KMeans  # 导入K-Means以拟合候选簇数
from sklearn.metrics import silhouette_score,adjusted_rand_score  # 导入分类或聚类指标以评价样本外结果
silhouette_scores={cluster_count:silhouette_score(x_now,KMeans(cluster_count,n_init=20,random_state=42).fit_predict(x_now)) for cluster_count in range(2,7)}  # 生成silhouette_scores以承接当前分析步骤
best_cluster_count=max(silhouette_scores,key=silhouette_scores.get)  # 选择轮廓系数最高的候选簇数
current_labels=KMeans(best_cluster_count,n_init=20,random_state=42).fit_predict(x_now)  # 选择轮廓系数最高的候选簇数
previous_labels=KMeans(best_cluster_count,n_init=20,random_state=42).fit_predict(x_prev.loc[common_codes])  # 选择轮廓系数最高的候选簇数
current_common_labels=pd.Series(current_labels,index=x_now.index).loc[common_codes]  # 按共同主体对齐当前标签
stability_ari=adjusted_rand_score(previous_labels,current_common_labels); cluster_shares=pd.Series(current_labels).value_counts(normalize=True)  # 计算各簇样本占比以检查可运营规模
random_baseline=adjusted_rand_score(previous_labels,np.random.default_rng(42).permutation(current_common_labels))  # 生成固定置换基线以判断稳定性是否超越随机
adoption_conditions_met=silhouette_scores[best_cluster_count]>=.20 and stability_ari>=.60 and cluster_shares.min()>=.10 and stability_ari>random_baseline  # 选择轮廓系数最高的候选簇数
print({'K':best_cluster_count,'silhouette':silhouette_scores[best_cluster_count],'ARI':stability_ari,'shares':cluster_shares.to_dict(),'random':random_baseline,'conditions_met':adoption_conditions_met})  # 输出聚类质量和稳定性比较

教师参考解答|答案与说明 2

  • 解释答案:K-Means 只按特征距离形成行为簇,簇编号没有天然业务顺序;轮廓系数、ARI 与最小簇占比共同回答分离度、跨期稳定性和可运营规模。
  • 拓展应用答案|汽车制造业公司因子簇

教师参考解答|代码 4

展开代码(代码区可独立滚动)
target_industry='汽车制造业'  # 事先设定另一中国行业作为公司层级新案例对象
industry_codes=set(company_basic.loc[company_basic['industry_name'].eq(target_industry),'order_book_id'])  # 从真实公司主表取得行业证券代码
def build_industry_features(quarter):  # 按与主案例一致的因子口径构造行业截面
    industry_cross_section=valuation_panel.loc[(slice(None),quarter),factor_columns].dropna().reset_index('date',drop=True)  # 提取指定报告期完整因子
    industry_cross_section=industry_cross_section[industry_cross_section.index.isin(industry_codes)]  # 仅保留事先设定行业公司
    industry_cross_section=industry_cross_section.assign(log_market_cap=np.log(industry_cross_section['market_cap'])).drop(columns='market_cap')  # 采用与主案例一致的市值变换
    return (industry_cross_section-industry_cross_section.mean())/industry_cross_section.std()  # 在新案例行业内重新拟合标准化
industry_now=build_industry_features(quarter_now)  # 构造当前季度新案例行业截面
industry_prev=build_industry_features(quarter_prev)  # 构造上一季度新案例行业截面
industry_common_codes=industry_now.index.intersection(industry_prev.index)  # 对齐两季共同公司
if len(industry_common_codes)<20: raise ValueError('当前样本不符合模型使用条件')  # 样本不足时停止聚类采用
industry_labels_now=KMeans(best_cluster_count,n_init=20,random_state=42).fit_predict(industry_now)  # 用相同K拟合当前行业截面
industry_labels_prev=KMeans(best_cluster_count,n_init=20,random_state=42).fit_predict(industry_prev.loc[industry_common_codes])  # 用相同K拟合上一季度共同样本
industry_common_labels=pd.Series(industry_labels_now,index=industry_now.index).loc[industry_common_codes]  # 对齐当前季度共同公司标签
industry_ari=adjusted_rand_score(industry_labels_prev,industry_common_labels)  # 核对新案例行业跨期稳定性
industry_shares=pd.Series(industry_labels_now).value_counts(normalize=True)  # 计算新案例行业最小簇占比
industry_conditions_met=industry_ari>=.60 and industry_shares.min()>=.10  # 使用相同稳定性与规模门判断是否采用
print({'industry':target_industry,'rows_now':len(industry_now),'common_rows':len(industry_common_codes),'ARI':industry_ari,'shares':industry_shares.to_dict(),'conditions_met':industry_conditions_met})  # 输出新案例的样本量、稳定性和组别占比

教师参考解答|答案与说明 3

  • 拓展应用说明:输入只替换为真实汽车制造业公司代码,特征、K、跨期ARI和最小样本占比要求保持不变;共同样本不足或判断条件失败即 暂不采用模型,不得改写为会员营销、优惠券或坏账动作。
  • 参考结果:K、轮廓系数、跨季ARI、最小簇占比、随机基线与判断条件。另行核对:确认轮廓输入不含簇标签;按行业分层复算簇占比与质心原尺度。
  • 常见错误:把组别编号当作高低顺序;用公司因子分组支持会员、优惠券或坏账决策;行业结构变化后仍沿用原结果。